package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

type Field struct {
	name      string
	required  bool
	index     int
	ftype     int
	protoType string
	declType  string
	cType     string
	isBuffer  bool
	isList    bool
	repeated  bool
	packed    bool
}

type Message struct {
	name   string
	fields []*Field
}

func isSupportedWireType(ftype string) bool {
	switch ftype {
	case "int32", "string", "fixed64", "bytes":
		return true
	default:
		return false
	}
}

func isUserType(ftype string) bool {
	for _, m := range messages {
		if m.name == ftype {
			return true
		}
	}

	return false
}

func checkFieldType() {
	for _, m := range messages {
		for _, f := range m.fields {
			if !isSupportedWireType(f.protoType) && !isUserType(f.protoType) {
				log.Fatal("unsupported field type:", f.protoType)
			}

			if f.repeated && isSupportedWireType(f.protoType) {
				if !f.packed {
					log.Fatal("repeated field of varints type should use packed flag, name:", f.name)
				}
			}

			if f.packed && f.protoType != "int32" {
				log.Fatal("only int32 type support packed")
			}
		}
	}
}

func getWireType(f *Field) int {
	switch f.protoType {
	case "int32":
		if f.packed {
			return 2
		}
		return 0
	case "fixed64":
		return 1
	case "string", "bytes":
		return 2
	default:
		return 2
	}
}

func gi() string {
	x := ""
	for i := 0; i < indent; i++ {
		x = x + "\t"
	}
	return x
}

func refineFieldTypeInfo(f *Field) {
	switch f.protoType {
	case "int32":
		f.cType = "myint32"
		if f.repeated {
			if !f.packed {
				log.Fatal("int32 type repeated must specify 'packed'")
			}
			f.declType = "List*"
			f.isList = true
		} else {
			f.declType = "myint32"
		}
		break
	case "fixed64":
		f.cType = "myint64"
		f.declType = "myint64"
		if f.repeated {
			log.Fatal("fixed64 not supported repeated")
		}
		break
	case "string":
		f.declType = "unsigned char*"
		f.cType = "unsigned char"
		f.isBuffer = true
		if f.repeated {
			log.Fatal("fixed64 not supported repeated")
		}
		break
	case "bytes":
		f.declType = "unsigned char*"
		f.cType = "unsigned char"
		f.isBuffer = true
		if f.repeated {
			log.Fatal("fixed64 not supported repeated")
		}
		break
	default:
		if !isUserType(f.protoType) {
			log.Fatal("unknown type:", f.protoType)
		}
		f.cType = f.protoType + "*"
		if f.repeated {
			f.declType = "List*"
			f.isList = true
		} else {
			f.declType = f.protoType + "*"
		}

		break
	}
}

func parseMessageName(line string) string {
	nametks := strings.Fields(line)
	// log.Println(nametks)
	if len(nametks) != 3 {
		log.Println("Error line is:", line)
		log.Fatal("parseMessageName err: name line must only have 3 elements, now is:", len(nametks))
	}

	if nametks[0] != "message" {
		log.Fatal("parseMessageName err: name line first element should be 'message' now is:", nametks[0])
	}

	if nametks[2] != "{" {
		log.Fatal("parseMessageName err: name line last element should be '{' now is:", nametks[0])
	}

	return nametks[1]
}

func parseField(fieldtks []string) *Field {
	if len(fieldtks) < 5 {
		log.Fatal("field row must contain at least 6 token, now is:", len(fieldtks))
	}

	field := Field{}
	switch fieldtks[0] {
	case "required":
		field.required = true
		break
	case "optional":
		field.required = false
		break
	case "repeated":
		field.repeated = true
		break
	default:
		log.Fatal("field row first element must be 'requried' or 'optoinal' or 'repeated', now is:", fieldtks[0])
		break
	}

	field.protoType = fieldtks[1]
	field.name = fieldtks[2]
	var err error
	field.index, err = strconv.Atoi(fieldtks[4])
	if err != nil {
		log.Fatal("field row 5 element can't convert to integer")
	}

	if len(fieldtks) > 5 && strings.Contains(fieldtks[5], "packed") && strings.Contains(fieldtks[5], "true") {
		field.packed = true
	}

	return &field
}

func parseMessage(lines []string) *Message {
	log.Println("parseMessage, lines:", lines)
	msg := Message{}
	msg.fields = make([]*Field, 0, 16)
	msg.name = parseMessageName(lines[0])
	lines = lines[1:(len(lines))]

	for _, line := range lines {
		fieldtks := strings.Fields(line)
		if len(fieldtks) < 5 {
			continue
		}

		field := parseField(fieldtks)

		msg.fields = append(msg.fields, field)
	}

	if len(msg.fields) == 0 {
		log.Fatal("message have no field:", msg.name)
	}

	return &msg
}

func trimComments(lines []string) []string {
	newlines := make([]string, 0, len(lines))

	for i := 0; i < len(lines); {
		var ln = lines[i]
		ln = strings.TrimSpace(ln)
		if strings.Index(ln, "//") == 0 {
			i++
			continue
		}

		if strings.Contains(ln, "/*") {
			j := i + 1
			for j < len(lines) {
				if strings.Contains(lines[j], "*/") {
					break
				}
				j++
			}

			i = j + 1
			continue
		}

		ln = strings.Trim(ln, ";")
		newlines = append(newlines, ln)
		i++
	}
	return newlines
}

func findMessageBeginLine(lines []string) int {
	for li, ln := range lines {
		if strings.Contains(ln, "message") {
			return li
		}
	}

	return -1
}

func findMessageEndLine(lines []string) int {
	for li, ln := range lines {
		if strings.Contains(ln, "}") {
			return li
		}
	}

	log.Fatal("can't find } for message")
	return -1
}

func parseMessages(lines []string) {
	for len(lines) > 0 {
		b := findMessageBeginLine(lines)
		if b < 0 {
			return
		}

		e := findMessageEndLine(lines[b+1:])

		msg := parseMessage(lines[b : b+e+2])
		messages = append(messages, msg)

		lines = lines[b+e+3:]
	}
}

func sortByDependent() {
	var i, j int
	for i = 0; i < len(messages); i++ {
		var x = messages[i]

		for j = i + 1; j < len(messages); j++ {
			var y = messages[j]

			for _, f := range x.fields {
				if f.protoType == y.name {
					messages[i], messages[j] = messages[j], messages[i]
					break
				}
			}
		}
	}
}

func genCalcFuncForF(lines []string, f *Field, m *Message) []string {
	if f.isList {
		lines = append(lines, gi()+fmt.Sprintf("do {"))
		indent++
		lines = append(lines, gi()+fmt.Sprintf("myint16 myPackedSize = 0;"))
		lines = append(lines, gi()+fmt.Sprintf("ListItem* item = ptr%s->%s->header;", m.name, f.name))
		lines = append(lines, gi()+fmt.Sprintf("while(item){"))
		indent++
		lines = append(lines, gi()+fmt.Sprintf("%s v = (%s)item->data;", f.cType, f.cType))
		if f.packed {
			lines = append(lines, gi()+fmt.Sprintf("myPackedSize += varintsSize(v);"))
		} else {
			lines = append(lines, gi()+fmt.Sprintf("myPackedSize = calc%sSize(v);", f.protoType))
			lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(MAKE_WIRE(%d, WIRE_TYPE_FIXLENGTH));", f.index))
			lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(myPackedSize);"))
			lines = append(lines, gi()+fmt.Sprintf("mySize += myPackedSize;"))
		}
		lines = append(lines, gi()+fmt.Sprintf("item = item->next;"))
		indent--
		lines = append(lines, gi()+"}")
		if f.packed {
			lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(MAKE_WIRE(%d, WIRE_TYPE_FIXLENGTH));", f.index))
			lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(myPackedSize);"))
			lines = append(lines, gi()+fmt.Sprintf("mySize += myPackedSize;"))
		}
		indent--
		lines = append(lines, gi()+"}while(0);\r\n")
	} else {
		switch f.protoType {
		case "int32":
			if !f.required {
				lines = append(lines, gi()+fmt.Sprintf("if (ptr%s->has%s > 0){", m.name, f.name))
				indent++
				lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(MAKE_WIRE(%d, WIRE_TYPE_VARINTS));", f.index))
				lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(ptr%s->%s);", m.name, f.name))
				indent--
				lines = append(lines, gi()+"}")
			} else {
				lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(MAKE_WIRE(%d, WIRE_TYPE_VARINTS));", f.index))
				lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(ptr%s->%s);", m.name, f.name))
			}

			break
		case "fixed64":
			if !f.required {
				lines = append(lines, gi()+fmt.Sprintf("if (ptr%s->has%s > 0){", m.name, f.name))
				indent++
				lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(MAKE_WIRE(%d, WIRE_TYPE_64BIT));", f.index))
				lines = append(lines, gi()+"mySize += 8;")
				indent--
				lines = append(lines, gi()+"}")
			} else {
				lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(MAKE_WIRE(%d, WIRE_TYPE_64BIT));", f.index))
				lines = append(lines, gi()+"mySize += 8;")
			}

			break
		case "bytes", "string":
			lines = append(lines, gi()+fmt.Sprintf("if (ptr%s->%sSize > 0){", m.name, f.name))
			indent++
			lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(MAKE_WIRE(%d, WIRE_TYPE_FIXLENGTH));", f.index))
			lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(ptr%s->%sSize);", m.name, f.name))
			lines = append(lines, gi()+fmt.Sprintf("mySize += ptr%s->%sSize;", m.name, f.name))
			indent--
			lines = append(lines, gi()+"}")
			break
		default:
			if !isUserType(f.protoType) {
				log.Fatal("require use type here:", f.protoType)
			}

			if !f.required {
				lines = append(lines, gi()+fmt.Sprintf("if (ptr%s->has%s > 0){", m.name, f.name))
				indent++
			}
			lines = append(lines, gi()+"do {")
			indent++
			lines = append(lines, gi()+"myint16 myPackedSize = 0;")
			lines = append(lines, gi()+fmt.Sprintf("myPackedSize = calc%sSize(ptr%s->%s);", f.protoType, m.name, f.name))
			lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(MAKE_WIRE(%d, WIRE_TYPE_FIXLENGTH));", f.index))
			lines = append(lines, gi()+fmt.Sprintf("mySize += varintsSize(myPackedSize);"))
			lines = append(lines, gi()+fmt.Sprintf("mySize += myPackedSize;"))
			indent--
			lines = append(lines, gi()+"} while(0);")
			if !f.required {
				indent--
				lines = append(lines, gi()+"}")
			}

			break
		}
	}

	return lines
}

func genCalcFuncForM(lines []string, m *Message) []string {
	indent = 0
	lines = append(lines, fmt.Sprintf("myint16 calc%sSize(%s* ptr%s) {", m.name, m.name, m.name))
	indent++
	lines = append(lines, gi()+fmt.Sprintf("myint16 mySize=0;"))

	// gen field calc
	for _, f := range m.fields {
		lines = genCalcFuncForF(lines, f, m)
	}

	lines = append(lines, gi()+"return mySize;")
	indent--
	lines = append(lines, "}")
	return lines
}

func genEncodeFuncForF(lines []string, f *Field, m *Message) []string {
	if f.repeated {

		lines = append(lines, gi()+fmt.Sprintf("if(!listIsEmpty(ptr%s->%s)){", m.name, f.name))
		indent++
		if f.packed {
			lines = append(lines, gi()+fmt.Sprintf("encodeWire(buffer, length, %d, WIRE_TYPE_FIXLENGTH);", f.index))
			lines = append(lines, gi()+fmt.Sprintf("encodePackedVarints(buffer, length, ptr%s->%s);", m.name, f.name))
		} else {
			lines = append(lines, gi()+fmt.Sprintf("myint16 myPackedSize = 0;"))
			lines = append(lines, gi()+fmt.Sprintf("ListItem* item = ptr%s->%s->header;", m.name, f.name))
			lines = append(lines, gi()+fmt.Sprintf("while(item){"))
			indent++
			lines = append(lines, gi()+fmt.Sprintf("%s v = (%s)item->data;", f.cType, f.cType))
			lines = append(lines, gi()+fmt.Sprintf("myPackedSize = calc%sSize(v);", f.protoType))
			lines = append(lines, gi()+fmt.Sprintf("encodeWire(buffer, length, %d, WIRE_TYPE_FIXLENGTH);", f.index))
			lines = append(lines, gi()+"encodeVarints(buffer, length, myPackedSize);")
			lines = append(lines, gi()+fmt.Sprintf("encode%s(buffer, length, v);", f.protoType))
			lines = append(lines, gi()+"item = item->next;")
			indent--
			lines = append(lines, gi()+"}")
		}
		indent--
		lines = append(lines, gi()+"}\r\n")
	} else {
		switch f.protoType {
		case "int32":
			if f.required {
				lines = append(lines, gi()+fmt.Sprintf("encodeWire(buffer, length, %d, WIRE_TYPE_VARINTS);", f.index))
				lines = append(lines, gi()+fmt.Sprintf("encodeVarints(buffer, length, ptr%s->%s);", m.name, f.name))
			} else {
				lines = append(lines, gi()+fmt.Sprintf("if (ptr%s->has%s) {", m.name, f.name))
				indent++
				lines = append(lines, gi()+fmt.Sprintf("encodeWire(buffer, length, %d, WIRE_TYPE_VARINTS);", f.index))
				lines = append(lines, gi()+fmt.Sprintf("encodeVarints(buffer, length, ptr%s->%s);", m.name, f.name))
				indent--
				lines = append(lines, gi()+"}")
			}
			break
		case "fixed64":
			if f.required {
				lines = append(lines, gi()+fmt.Sprintf("encodeWire(buffer, length, %d, WIRE_TYPE_64BIT);", f.index))
				lines = append(lines, gi()+fmt.Sprintf("encode64bit(buffer, length, ptr%s->%s);", m.name, f.name))
			} else {
				lines = append(lines, gi()+fmt.Sprintf("if (ptr%s->has%s) {", m.name, f.name))
				indent++
				lines = append(lines, gi()+fmt.Sprintf("encodeWire(buffer, length, %d, WIRE_TYPE_64BIT);", f.index))
				lines = append(lines, gi()+fmt.Sprintf("encode64bit(buffer, length, ptr%s->%s);", m.name, f.name))
				indent--
				lines = append(lines, gi()+"}")
			}
			break
		case "bytes", "string":
			lines = append(lines, gi()+fmt.Sprintf("if (ptr%s->%sSize > 0) {", m.name, f.name))
			indent++
			lines = append(lines, gi()+fmt.Sprintf("encodeWire(buffer, length, %d, WIRE_TYPE_FIXLENGTH);", f.index))
			lines = append(lines, gi()+fmt.Sprintf("encodeLengthDelimited(buffer, length, ptr%s->%sSize, (unsigned char*)ptr%s->%s);", m.name, f.name, m.name, f.name))
			indent--
			lines = append(lines, gi()+"}")
			break
		}
	}

	return lines
}

func genEncodeFuncForM(lines []string, m *Message) []string {
	indent = 0
	lines = append(lines, fmt.Sprintf("myint16 encode%s(unsigned char** buffer, myint16* length, %s* ptr%s) {", m.name, m.name, m.name))
	indent++
	lines = append(lines, gi()+"myint16 mySize = 0;")

	lines = append(lines, gi()+fmt.Sprintf("mySize = calc%sSize(ptr%s);", m.name, m.name))
	lines = append(lines, gi()+"if (mySize > *length) {")
	indent++
	lines = append(lines, gi()+"return ERR_ENCODE_NOT_ENOUGHBUFFER;")
	indent--
	lines = append(lines, gi()+"}")

	// gen field calc
	for _, f := range m.fields {
		lines = genEncodeFuncForF(lines, f, m)
	}

	lines = append(lines, gi()+"return mySize;")
	indent--
	lines = append(lines, "}")
	return lines
}

func genDecodeFuncForF(lines []string, f *Field, m *Message) []string {
	lines = append(lines, gi()+fmt.Sprintf("case %d:", f.index))
	lines = append(lines, gi()+"do {")
	indent++
	var wiretype = getWireType(f)
	lines = append(lines, gi()+fmt.Sprintf("if (type != %d){", wiretype))
	indent++
	lines = append(lines, gi()+"return ERR_DECODE_TYPE_NOT_MATCH;")
	indent--
	lines = append(lines, gi()+"}")
	switch wiretype {
	case 2:
		if f.isBuffer {
			lines = append(lines, gi()+fmt.Sprintf("ptr%s->%sSize = decodeVarints(buffer, length);", m.name, f.name))
			lines = append(lines, gi()+fmt.Sprintf("if (ptr%s->%s == 0) {", m.name, f.name))
			indent++
			lines = append(lines, gi()+fmt.Sprintf("ptr%s->%s = (unsigned char*)mymalloc(ptr%s->%sSize+1);", m.name, f.name, m.name, f.name))
			lines = append(lines, gi()+fmt.Sprintf("ptr%s->%s[ptr%s->%sSize] = 0;", m.name, f.name, m.name, f.name))
			indent--
			lines = append(lines, gi()+"}")
			lines = append(lines, gi()+fmt.Sprintf("decodeLengthDelimited(buffer, length, ptr%s->%sSize, (unsigned char*)ptr%s->%s, ptr%s->%sSize);",
				m.name, f.name, m.name, f.name, m.name, f.name))
			if !f.required {
				lines = append(lines, gi()+fmt.Sprintf("ptr%s->has%s = 1;", m.name, f.name))
			}
		} else if f.isList {
			lines = append(lines, gi()+"myint32 myTemp;")
			lines = append(lines, gi()+fmt.Sprintf("if (ptr%s->%s == 0) {", m.name, f.name))
			indent++
			lines = append(lines, gi()+fmt.Sprintf("ptr%s->%s = listCreate();", m.name, f.name))
			indent--
			lines = append(lines, gi()+"}")

			if f.packed {

				lines = append(lines, gi()+fmt.Sprintf("myTemp = decodePackedVarints(buffer, length, ptr%s->%s);", m.name, f.name))
				lines = append(lines, gi()+"if (myTemp < 0){")
				indent++
				lines = append(lines, gi()+"return myTemp;")
				indent--
				lines = append(lines, gi()+"}")
			} else if f.repeated {
				lines = append(lines, gi()+fmt.Sprintf("%s ptrObject = create%s();", f.cType, f.protoType))
				lines = append(lines, gi()+fmt.Sprintf("listPushBack(ptr%s->%s, (cvtype)ptrObject);", m.name, f.name))
				lines = append(lines, gi()+fmt.Sprintf("myTemp=decodeVarints(buffer, length);"))
				lines = append(lines, gi()+fmt.Sprintf("myTemp=decode%s(buffer, (myint16*)&myTemp, ptrObject);", f.protoType))
				lines = append(lines, gi()+fmt.Sprintf("if(myTemp < 0){"))
				indent++
				lines = append(lines, gi()+fmt.Sprintf("return myTemp;"))
				indent--
				lines = append(lines, gi()+"}")
				lines = append(lines, gi()+fmt.Sprintf("*length = *length - myTemp;"))
			}
		} else {
			lines = append(lines, gi()+"myint32 myTemp;")
			lines = append(lines, gi()+fmt.Sprintf("if (ptr%s->%s == 0) {", m.name, f.name))
			indent++
			lines = append(lines, gi()+fmt.Sprintf("ptr%s->%s = create%s();", m.name, f.name, f.protoType))
			indent--
			lines = append(lines, gi()+"}")
			lines = append(lines, gi()+fmt.Sprintf("myTemp=decodeVarints(buffer, length);"))
			lines = append(lines, gi()+fmt.Sprintf("myTemp=decode%s(buffer, (myint16*)&myTemp, ptr%s->%s);", f.protoType, m.name, f.name))
			lines = append(lines, gi()+fmt.Sprintf("if(myTemp < 0){"))
			indent++
			lines = append(lines, gi()+fmt.Sprintf("return myTemp;"))
			indent--
			lines = append(lines, gi()+"}")
			lines = append(lines, gi()+fmt.Sprintf("*length = *length - myTemp;"))
		}

		break
	case 0:
		lines = append(lines, gi()+"myint32 myTemp;")
		lines = append(lines, gi()+fmt.Sprintf("myTemp = decodeVarints(buffer, length);"))
		lines = append(lines, gi()+fmt.Sprintf("ptr%s->%s = myTemp;", m.name, f.name))
		if !f.required {
			lines = append(lines, gi()+fmt.Sprintf("ptr%s->has%s = 1;", m.name, f.name))
		}
		break
	case 1:
		//lines = append(lines, fmt.Sprintf("\t\t\tmyTemp = decode64bit(buffer, length);"))
		lines = append(lines, gi()+fmt.Sprintf("ptr%s->%s = decode64bit(buffer, length);", m.name, f.name))
		if !f.required {
			lines = append(lines, gi()+fmt.Sprintf("ptr%s->has%s = 1;", m.name, f.name))
		}
		break
	default:
		log.Fatal("unsupport wire type:%d", wiretype)
		break
	}

	indent--
	lines = append(lines, gi()+"}while(0);")
	lines = append(lines, gi()+fmt.Sprintf("break;"))
	return lines
}

func genDecodeFuncForM(lines []string, m *Message) []string {
	indent = 0
	lines = append(lines, fmt.Sprintf("myint16 decode%s(unsigned char** buffer, myint16* length, %s* ptr%s) {", m.name, m.name, m.name))
	indent++
	lines = append(lines, gi()+fmt.Sprintf("myint16 mySize = *length;"))
	lines = append(lines, gi()+fmt.Sprintf("myint32 wire;"))
	lines = append(lines, gi()+fmt.Sprintf("myint8 type;"))
	lines = append(lines, gi()+fmt.Sprintf("myint16 index;"))

	lines = append(lines, gi()+"while (*length > 0) {")
	indent++
	lines = append(lines, gi()+"wire = decodeVarints(buffer, length);")
	lines = append(lines, gi()+"type = (wire & 0x07);")
	lines = append(lines, gi()+"index = (myint16)(wire >> 3);")
	lines = append(lines, gi()+"switch (index) {")
	indent++

	for _, f := range m.fields {
		lines = genDecodeFuncForF(lines, f, m)
	}

	lines = append(lines, gi()+"default:")
	lines = append(lines, gi()+"break;")
	indent--
	lines = append(lines, gi()+"}")
	indent--
	lines = append(lines, gi()+"}")

	lines = append(lines, gi()+"return mySize - *length;")
	lines = append(lines, "}")
	return lines
}

func genCreateFuncForM(lines []string, m *Message) []string {
	indent = 0
	lines = append(lines, fmt.Sprintf("%s* create%s(void) {", m.name, m.name))
	indent++
	lines = append(lines, gi()+fmt.Sprintf("%s* obj = (%s*)mymalloc(sizeof(%s));", m.name, m.name, m.name))
	lines = append(lines, gi()+"memset(obj, 0, sizeof(*obj));")
	lines = append(lines, gi()+"return obj;")
	indent--
	lines = append(lines, "}")
	return lines
}

func genDestroyFuncForM(lines []string, m *Message) []string {
	indent = 0
	lines = append(lines, fmt.Sprintf("void destroy%s(%s* obj) {", m.name, m.name))
	indent++
	for _, f := range m.fields {
		if f.isBuffer {
			lines = append(lines, gi()+fmt.Sprintf("myfree(obj->%s);", f.name))
		} else if f.isList {
			lines = append(lines, gi()+fmt.Sprintf("if(obj->%s) {", f.name))
			indent++
			lines = append(lines, gi()+fmt.Sprintf("ListItem* item = obj->%s->header;", f.name))
			lines = append(lines, gi()+"while(item) {")
			indent++
			if isUserType(f.protoType) {
				lines = append(lines, gi()+fmt.Sprintf("%s* v = (%s*)item->data;", f.protoType, f.protoType))
				lines = append(lines, gi()+fmt.Sprintf("destroy%s(v);", f.protoType))
			} else if f.packed {
				lines = append(lines, gi()+fmt.Sprintf("%s* v = (%s*)item->data;", f.cType, f.cType))
				lines = append(lines, gi()+"myfree(v);")
			}

			lines = append(lines, gi()+"item = item->next;")
			indent--
			lines = append(lines, gi()+"}")

			lines = append(lines, gi()+fmt.Sprintf("listDestroy(obj->%s);", f.name))
			indent--
			lines = append(lines, gi()+"}")
		} else if isUserType(f.protoType) {
			lines = append(lines, gi()+fmt.Sprintf("if(obj->%s) {", f.name))
			indent++
			lines = append(lines, gi()+fmt.Sprintf("destroy%s(obj->%s);", f.protoType, f.name))
			indent--
			lines = append(lines, gi()+"}")
		}
	}
	lines = append(lines, gi()+"myfree(obj);")
	indent--
	lines = append(lines, "}")
	return lines
}

func genImplementFile() {
	lines := make([]string, 0, 1024)
	lines = append(lines, "/**")
	lines = append(lines, " *-----------------------------------------------------------------")
	lines = append(lines, " * This file is auto generated by stproto program, don't edit it!")
	lines = append(lines, " *-----------------------------------------------------------------")
	lines = append(lines, " */")
	lines = append(lines, fmt.Sprintf("#include \"%s.h\"", fileName))
	lines = append(lines, "#include <stdlib.h>")
	lines = append(lines, "#include <string.h>")
	lines = append(lines, "\r\n")
	for _, m := range messages {
		lines = genCalcFuncForM(lines, m)
	}
	for _, m := range messages {
		//if strings.Index(m.name, "SC") != 0 {
		lines = genEncodeFuncForM(lines, m)
		//}
	}
	for _, m := range messages {
		//if strings.Index(m.name, "CS") != 0 {
		lines = genDecodeFuncForM(lines, m)
		//}
	}
	for _, m := range messages {
		lines = genCreateFuncForM(lines, m)
	}
	for _, m := range messages {
		lines = genDestroyFuncForM(lines, m)
	}
	var str string = ""
	for _, ln := range lines {
		ln = ln + "\r\n"
		str = str + strings.Replace(ln, "\t", "    ", -1)
	}

	lines = append(lines, "\r\n")
	implementFilePath := path.Join(outputPath, fileName+".c")
	log.Println("output implement file:", implementFilePath)
	err := ioutil.WriteFile(implementFilePath, []byte(str), os.ModePerm)
	if err != nil {
		log.Println("output implement file error:", err)
	}
}

func genHeaderFileForF(lines []string, f *Field) []string {
	lines = append(lines, fmt.Sprintf("\t%s %s;", f.declType, f.name))
	if !f.required && !f.repeated {
		lines = append(lines, fmt.Sprintf("\tmyint8 has%s;", f.name))
	}

	if f.isBuffer {
		lines = append(lines, fmt.Sprintf("\tmyint16 %sSize;", f.name))
	}

	if f.repeated && f.declType != "List*" {
		log.Println("error filed declType is:", f.declType)
		log.Fatal("repeated type must be a List* type, field name:", f.name)
	}

	return lines
}

func genHeaderFileForM(lines []string, m *Message) []string {
	log.Println("begin to gen header file for message:", m.name)
	lines = append(lines, fmt.Sprintf("typedef struct _%s{", m.name))

	for _, f := range m.fields {
		refineFieldTypeInfo(f)
	}

	for _, f := range m.fields {
		lines = genHeaderFileForF(lines, f)
	}

	lines = append(lines, fmt.Sprintf("} %s;", m.name))
	lines = append(lines, "")
	return lines
}

func genFuncDeclForM(lines []string, m *Message) []string {
	lines = append(lines, fmt.Sprintf("myint16 calc%sSize(%s* ptr%s);", m.name, m.name, m.name))
	lines = append(lines, fmt.Sprintf("myint16 decode%s(unsigned char** buffer, myint16* length, %s* ptr%s);", m.name, m.name, m.name))
	lines = append(lines, fmt.Sprintf("myint16 encode%s(unsigned char** buffer, myint16* length, %s* ptr%s);", m.name, m.name, m.name))
	lines = append(lines, fmt.Sprintf("%s* create%s(void);", m.name, m.name))
	lines = append(lines, fmt.Sprintf("void destroy%s(%s*m);", m.name, m.name))
	lines = append(lines, "")
	return lines
}

func genHeaderFile() {
	lines := make([]string, 0, 1024)
	lines = append(lines, "/**")
	lines = append(lines, " *-----------------------------------------------------------------")
	lines = append(lines, " * This file is auto generated by stproto program, don't edit it!")
	lines = append(lines, " *-----------------------------------------------------------------")
	lines = append(lines, " */")
	lines = append(lines, "#ifndef _OCEAN_PROTO_")
	lines = append(lines, "#define _OCEAN_PROTO_")
	lines = append(lines, "#include \"coder.h\"")
	lines = append(lines, "#include \"list.h\"")
	lines = append(lines, "\r\n")
	for _, m := range messages {
		lines = genHeaderFileForM(lines, m)
	}

	lines = append(lines, "//--------------------funcs-------------------//")
	for _, m := range messages {
		lines = genFuncDeclForM(lines, m)
	}

	lines = append(lines, "#endif")

	var str string = ""
	for _, ln := range lines {
		ln = ln + "\r\n"
		str = str + strings.Replace(ln, "\t", "    ", -1)
	}
	lines = append(lines, "\r\n")

	headerFilePath := path.Join(outputPath, fileName+".h")
	log.Println("output header file:", headerFilePath)
	err := ioutil.WriteFile(headerFilePath, []byte(str), os.ModePerm)
	if err != nil {
		log.Println("output header file error:", err)
	}
}

func genMessageCode() {
	genHeaderFile()
	genImplementFile()
}

var (
	protoFilepath string
	outputPath    string
	fileName      string
	messages      []*Message
	indent        int
)

func init() {
	// example with short version for long flag
	flag.StringVar(&protoFilepath, "f", "", "specify the proto file path name")
	flag.StringVar(&outputPath, "o", ".", "specify the output path")

	messages = make([]*Message, 0, 256)
}

func main() {
	flag.Parse()

	if protoFilepath == "" {
		log.Println("please input proto file")
		return
	}

	pwd, _ := os.Getwd()
	log.Println("current work directory:", pwd)
	log.Println("command line output path:", outputPath)
	outputPath = path.Join(pwd, outputPath)
	protoFilepath = path.Join(pwd, protoFilepath)
	fileName = filepath.Base(protoFilepath)

	log.Printf("ouput:%s, file:%s, filename:%s\r\n", outputPath, protoFilepath, fileName)
	log.Println("now begin to parse proto file:", protoFilepath)

	filedata, err := ioutil.ReadFile(protoFilepath)
	if err != nil {
		log.Fatal("read file error:", err)
	}

	strfy := string(filedata)
	lines := strings.Split(strfy, "\r\n")

	log.Println("total line count:", len(lines))

	lines = trimComments(lines)
	// log.Println("remove commets:", lines)
	parseMessages(lines)

	log.Println("message count:", len(messages))

	sortByDependent()

	checkFieldType()

	genMessageCode()

}
